/** 
 * Solver.java
 * PROJECT 3: Sliding Block Puzzle Solver
 * @author Jonathan Kotker	cs61b-ar
 * @author Wei Zhong Yeh	cs61b-av
 * 
 * Main class that directs other classes to come up with the solution to solving the puzzle.
 */

import java.io.*;
import java.util.*;

public class Solver {
	
	/**
	 * Sets up Tray objects and Block objects in order to find the solution;
	 * the controller that directs other classes to do work
	 * @param args  String array containing the initial tray configuration, goal configuration, and possibly debugging options.
	 */
	public static void main (String [ ] args) {
		// detect bad arguments
		if (args.length != 2 && args.length != 3) {
			System.out.println ("Invalid number of arguments.");
			System.exit (1);
		}
		
		// REMOVE!
		Timer time = new Timer ( );
		time.start ( );
		
		// determine whether any debugging output has been asked for
		if ((args [0]).startsWith ("-o")) {
			// send the message to the Debug class
			Debug.debugOption (args [0].substring (2));
			// shift the arguments one to the left for less complicated code forthwith
			args [0] = args [1];
			args [1] = args [2];
			args [2] = null;
		}
		
		// set up Scanners to construct tray, blocks, and other data
		Scanner initialConfigFile = null;
		Scanner goalConfigFile = null;
		
		// attempt to check for files for necessary input
		try {
			File a = new File (args [0]);
			File b = new File (args [1]);
			initialConfigFile = new Scanner (a);
			goalConfigFile = new Scanner (b);
		} catch (FileNotFoundException e) {
			System.out.println ("File(s) cannot be found.");
			return;
		}
		
		// Integers holding the tray length and width, which are the first two tokens in initialConfigFile
		Integer trayLength = initialConfigFile.nextInt ( );
		Integer trayWidth = initialConfigFile.nextInt ( );
		
		// set up an initial tray
		Tray tray = new Tray ( );
		// set up blocks from the rest of initialConfigFile and add to the created tray
		while (initialConfigFile.hasNextInt ( )) {
			// set up a single block based on its length and width, and the row and
			// column of upper left corner, and add the block to the tray
			tray.addBlock (new Block (initialConfigFile.nextInt ( ), initialConfigFile.nextInt ( ),
									  initialConfigFile.nextInt ( ), initialConfigFile.nextInt ( )));
		}
		
		// set up a goal configuration using the information from goalConfigFile
		Goal goalConfig = new Goal ( );
		// set up goal blocks and add to the goal tray
		while (goalConfigFile.hasNextInt ( )) {
			// set up a single block based on its length and width, and the row and
			// column of upper left corner, and add the block to the tray
			goalConfig.addBlock (new Block (goalConfigFile.nextInt ( ), goalConfigFile.nextInt ( ),
							 				goalConfigFile.nextInt ( ), goalConfigFile.nextInt ( )));
		}
		
		// make sure the initial tray doesn't satisfy the goal before proceeding
		if (! goalConfig.goalReached(tray)) {
			// REMOVE!
			// tray.setHeuristic (goalConfig);
			// set up the actual path finding class
			The pathFinder = new The (trayLength, trayWidth, tray, goalConfig);
			// find the solution (if there is one)
			Solver.printSolution (pathFinder.findSolution ( ));
		}
		// REMOVE!
		//System.out.println(time.stop());
	}
	
	/**
	 * Prints a solution to the provided sliding block puzzle based on the argument
	 * @param blockCoordinates  LinkedList of integer arrays representing moves {x1, y1, x2, y2}
	 */
	public static void printSolution (LinkedList <int [ ]> blockCoordinates) {
		// temporary variables to store the integer arrays as received
		int [ ] intArray, otherIntArray;
		// temporary LinkedList to store the integer arrays in reverse order
		LinkedList <int [ ]> actualOutputList = new LinkedList <int [ ]> ( );
		// temporary variable to store the size of the solution array
		int size;
		
		// if blockCoordinates is empty, it means no moves were recorded, and thus a solution doesn't exist
		if (blockCoordinates.size ( ) == 0)
			System.exit (1);
		
		// add the entries in the LinkedList to a new LinkedList, in reverse order
		for (int blockCounter = (blockCoordinates.size ( ) - 2); blockCounter >= 0; blockCounter --)
			actualOutputList.add (blockCoordinates.get (blockCounter));
		
		// collate the entries in the ArrayList, so that, for example, the moves {2, 0, 2, 1} and {2, 1, 2, 2}
		// are collected together as one move {2, 0, 2, 2}
		size = actualOutputList.size ( );
		for (int blockCounter = 0; blockCounter < (size - 1); ) {
			intArray = actualOutputList.get (blockCounter);
			otherIntArray = null;
			if (blockCounter < (size - 1))
				otherIntArray = actualOutputList.get (blockCounter + 1);
			if (otherIntArray != null) {
				// collation found
				if (((otherIntArray [0] == intArray [2]) && (otherIntArray [1] == intArray [3])) &&
						(	(Math.abs (intArray [0] - otherIntArray [2]) == 0) ?
								(Math.abs (intArray [1] - otherIntArray [3]) != 0) :
									(Math.abs (intArray [1] - otherIntArray [3]) == 0))) {
					// create a new array
					int [ ] newArray = {intArray [0], intArray [1], otherIntArray [2], otherIntArray [3]};
					actualOutputList.remove (blockCounter + 1);
					actualOutputList.add (blockCounter + 1, newArray);
					actualOutputList.remove (blockCounter);
					size = size - 1;
				} // collation not found
				else
					blockCounter++;
			}
		}
		
		// print the solution
		Iterator <int [ ]> solution = actualOutputList.iterator ( ); 
		while (solution.hasNext ( )) {
			intArray = solution.next ( );
			for (int coordinates = 0; coordinates < 4; coordinates++) {
				System.out.print (intArray [coordinates]);
				if (coordinates != 3)
					System.out.print (" ");
			}
			System.out.println ( );
		}
	}
}